/*
 * Copyright (c) 2022 Winner Microelectronics Co., Ltd. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <string.h>
#include "wm_sasc.h"

extern void dumpBuffer(char *name, char* buffer, int len);

#define TEST_DEBUG_EN           1
#if TEST_DEBUG_EN
#define TEST_DEBUG(fmt, ...)    printf("%s: "fmt, __func__, ##__VA_ARGS__)
#else
#define TEST_DEBUG(fmt, ...)
#endif

static void switch_to_ree(void)
{
    csi_vic_disable_irq(SYS_TICK_IRQn);
    __asm__ volatile(
        "mtcr r14, cr<6, 3> \n"
        "mfcr     a3, psr\n"
        "bclri    a3, 30 \n" // Clear PSR.T
        "mtcr     a3, psr \n"
        "psrset   ee, ie \n" // Set EE & IE
        "mfcr     a3, psr\n"
        "bseti    a3, 9 \n" // Set MM
        "mtcr     a3, psr \n"
    );
    __asm__ volatile("mtcr %0, vbr" : : "r"(& (__Vectors)));
    TEST_DEBUG("switched to ree\n");
}

static SASC_Type* get_block_from_addr(uint32_t base_addr)
{
    if (base_addr >= 0x8000000 && base_addr < 0x10000000) {
        return SASC_FLASH;
    } else if (base_addr < 0x20028000 && base_addr >= 0x20000000) {
        return SASC_B1;
    } else if (base_addr < 0x20048000 && base_addr >= 0x20028000) {
        return SASC_B2;
    }
    return NULL;
}

static void wm_sasc_config_region(SASC_Type *block, uint32_t idx, uint32_t base_addr, sasc_region_size_e size,
    sasc_region_attr_t *attr, uint32_t enable)
{
    base_addr &= 0x03FFFFFC;
    block->REGION[idx] = size | (base_addr << 6);
    // printf("region %d: 0x%x\n", idx, size | (base_addr << 6));
    block->CAR = _R2_Pos(attr->car, idx) | (block->CAR & ~(_R2_Msk(idx)));
    switch (attr->car) {
        case SASC_UN_SE_USER:
            block->AP0 = _R2_Pos(attr->ap, idx) | (block->AP0 & ~(_R2_Msk(idx)));
            block->CD0 = _R2_Pos(attr->cd, idx) | (block->CD0 & ~(_R2_Msk(idx)));
            break;
        case SASC_UN_SE_SUPER:
            block->AP1 = _R2_Pos(attr->ap, idx) | (block->AP1 & ~(_R2_Msk(idx)));
            block->CD1 = _R2_Pos(attr->cd, idx) | (block->CD1 & ~(_R2_Msk(idx)));
            break;
        case SASC_SE_USER:
            block->AP2 = _R2_Pos(attr->ap, idx) | (block->AP2 & ~(_R2_Msk(idx)));
            block->CD2 = _R2_Pos(attr->cd, idx) | (block->CD2 & ~(_R2_Msk(idx)));
            break;
        default:
            break;
    }
    block->CR = _R1_Pos(enable, idx) | (block->CR & ~(_R1_Msk(idx)));
}

void wm_sasc_enable_region(SASC_Type *block, uint32_t idx)
{
    block->CR = _R1_Pos(1, idx) | (block->CR & ~(_R1_Msk(idx)));
}

void wm_sasc_disable_region(SASC_Type *block, uint32_t idx)
{
    block->CR = (block->CR & ~(_R1_Msk(idx)));
}

void set_region_protect(uint32_t base_addr, uint32_t idx, sasc_region_size_e size, sasc_region_attr_t *attr)
{
    SASC_Type* block = get_block_from_addr(base_addr);

    TEST_DEBUG("base_addr %x idx %u size 0x%x ap %d cd %d car %d\n", \
                base_addr, idx, size, attr->ap, attr->cd, attr->car);
    if (base_addr >= 0x8000000 && base_addr < 0x10000000) {
        base_addr &= 0x1FFFFFF;
    } else if (base_addr < 0x20028000 && base_addr >= 0x20000000) {
        base_addr &= 0x3FFFF;
    } else if (base_addr < 0x20048000 && base_addr >= 0x20028000) {
        base_addr &= 0x7FFFF;
    }
    wm_sasc_config_region(block, idx, base_addr, size, attr, 1);
}

/**
  *******************************************************
  *               TEST CODE IS BELOW
  *******************************************************
  */

void access_region(uint32_t base_addr, sasc_region_size_e size, sasc_region_attr_t *attr, int pos)
{
    char dest[4] = {0xa5, 0xa5, 0xa5, 0xa5};
    uint32_t s = 1 << (size - 3);
    TEST_DEBUG("base_addr %x size 0x%x\n", base_addr, s);

    switch (pos) {
        case 0:
            memcpy_s(dest, sizeof(dest), (char*)(base_addr + s), sizeof(dest));
            TEST_DEBUG("Behind addr %x size 0x%lx can read\n", base_addr + s, (unsigned long)sizeof(dest));
            TEST_DEBUG("start to read addr %x size 0x%lx\n", \
                        base_addr + s - (uint32_t)sizeof(dest), (unsigned long)sizeof(dest));
            memcpy_s(dest, sizeof(dest), (char*)(base_addr + s - sizeof(dest)), sizeof(dest));
            TEST_DEBUG("addr %x size 0x%lx can read\n", \
                        base_addr + s - (uint32_t)sizeof(dest), (unsigned long)sizeof(dest));
            break;
        case 1:
            memcpy_s(dest, sizeof(dest), (char*)(base_addr - sizeof(dest)), sizeof(dest));
            TEST_DEBUG("Front addr %x size 0x%lx can read\n", \
                       base_addr - (uint32_t)sizeof(dest), (unsigned long)sizeof(dest));
            TEST_DEBUG("start to read addr %x size 0x%lx\n", base_addr, (unsigned long)sizeof(dest));
            memcpy_s(dest, sizeof(dest), (char*)base_addr, sizeof(dest));
            TEST_DEBUG("base_addr %x size 0x%lx can read\n", base_addr, (unsigned long)sizeof(dest));
            break;
        case 2:
            memcpy_s((char*)(base_addr - sizeof(dest)), sizeof(base_addr -sizeof(dest)), dest, sizeof(dest));
            TEST_DEBUG("Front addr %x size 0x%lx can write\n", \
                       base_addr - (uint32_t)sizeof(dest), (unsigned long)sizeof(dest));
            TEST_DEBUG("start to write addr %x size 0x%lx\n", base_addr, (unsigned long)sizeof(dest));
            memcpy_s((char*)base_addr, sizeof(base_addr), dest, sizeof(dest));
            TEST_DEBUG("base_addr %x size 0x%lx can write\n", base_addr, (unsigned long)sizeof(dest));
            break;
        default:
            memcpy_s((char*)(base_addr + s), sizeof(base_addr + s), dest, sizeof(dest));
            TEST_DEBUG("Behind addr %x size 0x%lx can write\n", base_addr + s, (unsigned long)sizeof(dest));
            TEST_DEBUG("start to write addr %x size 0x%lx\n", \
                       base_addr + s - (uint32_t)sizeof(dest), (unsigned long)sizeof(dest));
            memcpy_s((char*)(base_addr + s - sizeof(dest)), sizeof(base_addr + s - sizeof(dest)), dest, sizeof(dest));
            TEST_DEBUG("addr %x size 0x%lx can write\n", \
                       base_addr + s - (uint32_t)sizeof(dest), (unsigned long)sizeof(dest));
            break;
    }
    dumpBuffer("dest", dest, sizeof(dest));
}

int sasc_region_security_test(int num)
{
    sasc_region_attr_t attr;
    attr.ap = SASC_AP_DENYALL;
    attr.cd = SASC_CD_DA_OF;
    attr.car = SASC_SE_SUPER;
    set_region_protect(0x08010000, 0, SASC_REGION_SIZE_64KB, &attr);
    set_region_protect(0x20010000, 1, SASC_REGION_SIZE_16KB, &attr);
    set_region_protect(0x20030000, 2, SASC_REGION_SIZE_32KB, &attr);

//   SASC_B2->REGION[0] = SASC_REGION_SIZE_32KB | (0x8000 << 6);

    switch_to_ree();

    switch (num) {
        case 0:
            access_region(0x20010000, SASC_REGION_SIZE_16KB, &attr, 0);
            break;
        case 1:
            access_region(0x20010000, SASC_REGION_SIZE_16KB, &attr, 1);
            break;
        case 2:
            access_region(0x20010000, SASC_REGION_SIZE_16KB, &attr, 2);
            break;
        case 3:
            access_region(0x20010000, SASC_REGION_SIZE_16KB, &attr, 3);
            break;
        case 4:
            access_region(0x20030000, SASC_REGION_SIZE_32KB, &attr, 0);
            break;
        case 5:
            access_region(0x20030000, SASC_REGION_SIZE_32KB, &attr, 1);
            break;
        case 6:
            access_region(0x20030000, SASC_REGION_SIZE_32KB, &attr, 2);
            break;
        case 7:
            access_region(0x20030000, SASC_REGION_SIZE_32KB, &attr, 3);
            break;
        case 8:
            access_region(0x08010000, SASC_REGION_SIZE_64KB, &attr, 0);
            break;
        case 9:
            access_region(0x08010000, SASC_REGION_SIZE_64KB, &attr, 1);
            break;
        default:
            break;
    }
    return -1;
}

